package org.lboot.flow.provider;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.Validator;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.lboot.core.auth.AuthService;
import org.lboot.core.exception.BusinessException;
import org.lboot.flow.FlowCtl;
import org.lboot.flow.config.FlowControlConfig;
import org.lboot.flow.constant.*;
import org.lboot.flow.event.FlowEventConsumer;
import org.lboot.flow.event.system.FlowCheckEvent;
import org.lboot.flow.event.system.FlowStartEvent;
import org.lboot.flow.event.user.FlowBackEvent;
import org.lboot.flow.event.user.FlowCancelEvent;
import org.lboot.flow.event.user.FlowDeleteEvent;
import org.lboot.flow.loader.TaskUserLoader;
import org.lboot.flow.model.FlowModel;
import org.lboot.flow.model.node.BaseModel;
import org.lboot.flow.model.node.DecisionTaskModel;
import org.lboot.flow.model.node.StartModel;
import org.lboot.flow.model.node.TaskModel;
import org.lboot.flow.module.assign.FlowTaskAssign;
import org.lboot.flow.module.assign.FlowTaskAssignService;
import org.lboot.flow.module.audit.FlowAudit;
import org.lboot.flow.module.audit.FlowAuditService;
import org.lboot.flow.module.define.FlowDefine;
import org.lboot.flow.module.define.FlowDefineService;
import org.lboot.flow.module.event.FlowCustomEventService;
import org.lboot.flow.module.event.FlowEvent;
import org.lboot.flow.module.work.FlowWork;
import org.lboot.flow.module.work.FlowWorkService;
import org.lboot.flow.module.work.params.FlowDecisionTaskParams;
import org.lboot.flow.module.work.params.FlowStartParams;
import org.lboot.flow.module.work.params.FlowTaskParams;
import org.lboot.flow.parser.FlowNodeParser;
import org.lboot.flow.parser.FlowParser;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Example;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author kindear
 * 操作类 不参与权限校验
 */
@Slf4j
@Service
@AllArgsConstructor
public class DefaultFlowCtlImpl implements FlowCtl {

    // 流程任务分配
    FlowTaskAssignService flowTaskAssignService;

    // 流程定义
    FlowDefineService flowDefineService;

    // 流程实例
    FlowWorkService flowWorkService;

    // 流程审计
    FlowAuditService flowAuditService;

    // 流程节点解析器
    FlowNodeParser flowNodeParser;

    // 流程流转配置
    FlowControlConfig flowControlConfig;

    // 鉴权服务
    AuthService authService;

    // 审核用户装载服务
    TaskUserLoader taskUserLoader;

    // 自定义事件服务
    FlowCustomEventService eventService;

    // 事件消费者列表
    List<FlowEventConsumer> consumers;

    // 流程实例解析列表
    List<FlowParser> flowParsers;


    @Resource
    ApplicationContext context;


    @SneakyThrows
    @Transactional
    @Override
    public FlowWork startFlow(FlowStartParams params) {
        // 获取流程定义实例
        List<FlowDefine> defines = flowDefineService.getFlowByCode(params.getFlowCode());
        if (defines.isEmpty()){
            throw new BusinessException(HttpStatus.NOT_FOUND,"未找到对应流程定义");
        }
        if (defines.size() > 1){
            log.warn("流程定义编码{}存在重复记录",params.getFlowCode());
        }
        FlowDefine define = defines.get(0);
        // 流程定义
        if (define.getStatus().equals(0)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"流程定义处于拒绝访问状态");
        }
        // 流程实例
        FlowWork flowWork = new FlowWork();
        // 获取节点解析信息
        List<BaseModel> nodeList = define.getNodeList();
        // 设置流程定义
        flowWork.setNodeList(nodeList);
        flowWork.setFlowCode(define.getFlowCode());
        // 流程名称
        if (Validator.isNotEmpty(params.getFlowName())){
            flowWork.setFlowName(params.getFlowName());
        }else {
            flowWork.setFlowName(define.getFlowName());
        }
        // 流程发起者
        String initiator = params.getInitiator();
        // 判断是不是当前登录用户 & 是否允许流程代理
        if (authService.getUid().equals(initiator)){
            flowWork.setInitiator(initiator);
        }else {
            if (flowControlConfig.enableProxyFlow()){
                flowWork.setInitiator(initiator);
            }else {
                throw new BusinessException(HttpStatus.FORBIDDEN,"请配置允许流程代理或本人提交流程");
            }
        }

        // 流程参数
        flowWork.setFlowParam(params.getFlowParam());
        flowWork.setBusinessId(params.getBusinessId());
        flowWork.setBusinessType(params.getBusinessType());
        flowWork.setBusinessName(params.getBusinessName());
        // 设置根流程
        flowWork.setRootFlowId(params.getRootFlowId());
        // 构建开始节点
        StartModel startModel = flowWork.getStartModel();
        flowWork.setCurrentNodeId(startModel.getId());
        flowWork.setCurrentNodeType(startModel.getNodeType());
        flowWork.setNextNodeId(startModel.getNextNodeId());
        // 判断流程提交允许范围
        if (!startModel.getEnableAll()){
            if (!checkActionPerm(flowWork)){
                throw new BusinessException(HttpStatus.FORBIDDEN,"无流程发起权限");
            }
        }
        // 设置流程状态
        flowWork.setStatus(1);
        flowWork.setFlowWorkStatus(FlowWorkStatusEnum.IN_PROCESS.value);
        // 存储
        FlowWork saveRes = flowWorkService.saveOne(flowWork);
        // 触发流自检事件
        context.publishEvent(new FlowCheckEvent(this,saveRes,null));
        return saveRes;
    }
    @Override
    public FlowWork deleteFlow(String flowWorkId) {
        // 获取对应流程实例
        FlowWork flowWork = flowWorkService.getOneUnwrap(flowWorkId);
        // 分支流程阻却事由
        if (Validator.isNotEmpty(flowWork.getRootFlowId())){
            throw new BusinessException(HttpStatus.FORBIDDEN,"分支流程仅支持关联删除");
        }
        flowWorkService.deleteOne(flowWorkId);
        // 发布流程实例删除事件
        context.publishEvent(new FlowDeleteEvent(this,flowWork));
        return flowWork;
    }

    @Override
    public FlowWork cancelFlow(String flowWorkId) {
        FlowWork flowWork = flowWorkService.getOneUnwrap(flowWorkId);
        // 获取流程状态
        if (flowWork.getStatus().equals(0)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"当前流程实例被挂起，无法操作");
        }
        // 根据流程实例状态区分
        if (flowWork.getFlowWorkStatus().equals(FlowWorkStatusEnum.FINISHED.value)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"流程已结束，无法操作");
        }
        if (flowWork.getFlowWorkStatus().equals(FlowWorkStatusEnum.CANCELED.value)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"流程已取消，无法操作");
        }
        if (flowWork.getFlowWorkStatus().equals(FlowWorkStatusEnum.CLOSED.value)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"流程已关闭，无法操作");
        }
        if (flowWork.getFlowWorkStatus().equals(FlowWorkStatusEnum.EXPIRED.value)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"流程已超时关闭，无法操作");
        }

        // 分支流程 -> 支持取消吗
        if (Validator.isNotEmpty(flowWork.getRootFlowId())){
            throw new BusinessException(HttpStatus.FORBIDDEN,"分支流程仅支持关联取消");
        }
        // 是否可以取消
        boolean canCancel = false;
        // 获取发起人ID
        String initiator = flowWork.getInitiator();
        String proxyUserId = flowWork.getCreateUserId();
        // 获取当前登录用户 / 代理发起用户 -> 相同或是管理员可以操作
        if (authService.getUid().equals(initiator)  || authService.isAdmin()){
           canCancel = true;
        }
        // 代理用户可取消
        if (flowControlConfig.enableProxyFlow()){
            if (authService.getUid().equals(proxyUserId)){
                canCancel = true;
            }
        }
        if (canCancel){
            flowWork.setFlowWorkStatus(FlowWorkStatusEnum.CANCELED.value);
            // 取消后的流程禁止访问
            flowWork.setStatus(0);
            flowWorkService.updateOne(flowWork);
            // 发布事件
            context.publishEvent(new FlowCancelEvent(this,flowWork));
        }else {
            throw new BusinessException(HttpStatus.FORBIDDEN,"无该流程取消权限");
        }
        return flowWork;
    }

    @Override
    @SneakyThrows
    public FlowWork backFlow(String flowWorkId, String backNodeId) {
        FlowWork flowWork = flowWorkService.getOneUnwrap(flowWorkId);
        FlowWork oldFlow = new FlowWork();
        BeanUtil.copyProperties(flowWork, oldFlow, CopyOptions.create().setIgnoreNullValue(true).setIgnoreError(true));
        // 获取流程状态
        if (flowWork.getStatus().equals(0)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"当前流程实例被挂起，无法操作");
        }
        // 根据流程实例状态区分
        if (flowWork.getFlowWorkStatus().equals(FlowWorkStatusEnum.FINISHED.value)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"流程已结束，无法操作");
        }
        if (flowWork.getFlowWorkStatus().equals(FlowWorkStatusEnum.CANCELED.value)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"流程已取消，无法操作");
        }
        if (flowWork.getFlowWorkStatus().equals(FlowWorkStatusEnum.CLOSED.value)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"流程已关闭，无法操作");
        }
        if (flowWork.getFlowWorkStatus().equals(FlowWorkStatusEnum.EXPIRED.value)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"流程已超时关闭，无法操作");
        }
        // 其余状态都可以操作
        BaseModel node = flowWork.getNodeModel(backNodeId);
        if (Validator.isEmpty(node)){
            throw new BusinessException(HttpStatus.BAD_REQUEST,"不存在对应回退节点");
        }
        String currentNodeId = flowWork.getCurrentNodeId();
        BaseModel curNode = flowWork.getNodeModel(currentNodeId);
        // 只有 TASK 节点可以执行回退
        if (curNode.getNodeType().equals(FlowNodeTypeConst.TASK_NODE)){
            TaskModel taskModel = (TaskModel) curNode;
            if (!taskModel.isEnableBack()){
                throw new BusinessException(HttpStatus.FORBIDDEN,"节点禁止回退操作");
            }
        }
        else if (curNode.getNodeType().equals(FlowNodeTypeConst.DECISION_TASK_NODE)){
            DecisionTaskModel model = (DecisionTaskModel) curNode;
            if (!model.isEnableBack()){
                throw new BusinessException(HttpStatus.FORBIDDEN,"节点禁止回退操作");
            }
        }else {
            throw new BusinessException(HttpStatus.FORBIDDEN,"节点禁止回退操作");
        }
        // 执行回退操作
        flowWork.setCurrentNodeId(backNodeId);
        flowWork.setNextNodeId(node.getNextNodeId());
        FlowWork result =  flowWorkService.updateOne(flowWork);
        // 发布时间
        context.publishEvent(new FlowBackEvent(this, oldFlow, result));
        return result;
    }

    @Override
    @SneakyThrows
    public FlowTaskAssign createAssign(String flowCode, String flowNodeId, String assignType, String assignId) {
        // 审核指派新增 -> 排除重复
        FlowTaskAssign assign = new FlowTaskAssign();
        assign.setFlowCode(flowCode);
        assign.setFlowNodeId(flowNodeId);
        // 审核指派类型限制
        if (!FlowAssignConst.isAssignType(assignType)){
            throw new BusinessException(HttpStatus.BAD_REQUEST,"审核指派类型不在可选范围");
        }
        assign.setAssignType(assignType);
        assign.setAssignId(assignId);
        Example<FlowTaskAssign> example = Example.of(assign);
        List<FlowTaskAssign> assigns = flowTaskAssignService.getExample(example);
        if (!assigns.isEmpty()){
            throw new BusinessException(HttpStatus.BAD_REQUEST,"存在重复审核指派");
        }
        // 获取流程定义实例
        List<FlowDefine> defines = flowDefineService.getFlowByCode(flowCode);
        if (defines.isEmpty()){
            throw new BusinessException(HttpStatus.NOT_FOUND,"未找到对应流程定义");
        }
        if (defines.size() > 1){
            log.warn("流程定义编码{}存在重复记录",flowCode);
        }
        FlowDefine define = defines.get(0);
        // 流程定义
        BaseModel baseModel = define.getNodeModel(flowNodeId);
        // 如果为空
        if (Validator.isEmpty(baseModel)){
            throw new BusinessException(HttpStatus.NOT_FOUND,"流程定义不存在对应节点");
        }
        // 写入审核指派
        return flowTaskAssignService.saveOne(assign);
    }

    @Override
    public FlowTaskAssign deleteAssign(String id) {
        FlowTaskAssign assign = flowTaskAssignService.getOneUnwrap(id);
        flowTaskAssignService.deleteOne(id);
        return assign;
    }


    @Override
    @SneakyThrows
    public boolean decisionTask(FlowDecisionTaskParams params) {
        String flowWorkId = params.getFlowWorkId();
        // 获取流程实例
        FlowWork flowWork = flowWorkService.getOneUnwrap(flowWorkId);
        if (flowWork.getStatus().equals(0)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"当前流程实例被挂起，无法操作");
        }
        // 校验操作权限
        if (!checkActionPerm(flowWork)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"无操作权限");
        }
        // 可操作节点类型
        if (!flowWork.getCurrentNodeType().equals(FlowNodeTypeConst.DECISION_TASK_NODE)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"流程节点不符");
        }
        // 写入流程审计
        FlowAudit audit = new FlowAudit();
        audit.setFlowNodeId(flowWork.getCurrentNodeId());
        // 获取当前节点名称
        audit.setFlowNodeName(flowWork.getCurrentModel().getName());
        audit.setFlowNodeType(flowWork.getCurrentNodeType());
        audit.setFlowWorkId(flowWork.getId());
        audit.setAuditResult(params.getDecision());
        Example<FlowAudit> example = Example.of(audit);
        List<FlowAudit> audits = flowAuditService.getExample(example);
        if (!audits.isEmpty()){
            throw new BusinessException(HttpStatus.FORBIDDEN,"重复操作");
        }
        audit.setRemark(params.getRemark());
        // 审计结果写入
        flowAuditService.saveOne(audit);
        context.publishEvent(new FlowCheckEvent(this,flowWork,audit));
        return true;
    }

    @Override
    public boolean completeTask(FlowTaskParams params) {
        String flowWorkId = params.getFlowWorkId();
        // 获取流程实例
        FlowWork flowWork = flowWorkService.getOneUnwrap(flowWorkId);
        if (flowWork.getStatus().equals(0)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"当前流程实例被挂起，无法操作");
        }
        if (!flowWork.getFlowWorkStatus().equals(FlowWorkStatusEnum.IN_PROCESS.value)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"非进行态流程，无法操作");
        }
        // 校验操作权限
        if (!checkActionPerm(flowWork)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"无操作权限");
        }
        // 可操作节点类型
        if (!flowWork.getCurrentNodeType().equals(FlowNodeTypeConst.TASK_NODE)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"流程节点不符");
        }
        // 写入流程审核意见 ->
        FlowAudit audit = new FlowAudit();
        audit.setFlowNodeId(flowWork.getCurrentNodeId());
        audit.setFlowNodeName(flowWork.getCurrentModel().getName());
        audit.setFlowNodeType(flowWork.getCurrentNodeType());
        audit.setFlowWorkId(params.getFlowWorkId());
        audit.setAuditResult(FlowTaskAuditConst.PASS);
        Example<FlowAudit> example = Example.of(audit);
        List<FlowAudit> audits = flowAuditService.getExample(example);
        if (!audits.isEmpty()){
            throw new BusinessException(HttpStatus.FORBIDDEN,"重复操作");
        }
        audit.setRemark(params.getRemark());
        flowAuditService.saveOne(audit);
        // 触发流程自检
        context.publishEvent(new FlowCheckEvent(this,flowWork,audit));
        return true;
    }

    @Override
    public boolean rejectTask(FlowTaskParams params) {
        String flowWorkId = params.getFlowWorkId();
        // 获取流程实例
        FlowWork flowWork = flowWorkService.getOneUnwrap(flowWorkId);
        if (flowWork.getStatus().equals(0)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"当前流程实例被挂起，无法操作");
        }
        if (!flowWork.getFlowWorkStatus().equals(FlowWorkStatusEnum.IN_PROCESS.value)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"非进行态流程，无法操作");
        }
        // 校验操作权限
        if (!checkActionPerm(flowWork)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"无操作权限");
        }
        // 可操作节点类型
        if (!flowWork.getCurrentNodeType().equals(FlowNodeTypeConst.TASK_NODE)){
            throw new BusinessException(HttpStatus.FORBIDDEN,"流程节点不符");
        }
        // 写入流程审核意见 ->
        FlowAudit audit = new FlowAudit();
        audit.setFlowNodeId(flowWork.getCurrentNodeId());
        audit.setFlowNodeName(flowWork.getCurrentModel().getName());
        audit.setFlowNodeType(flowWork.getCurrentNodeType());
        audit.setFlowWorkId(params.getFlowWorkId());
        audit.setAuditResult(FlowTaskAuditConst.REJECT);
        Example<FlowAudit> example = Example.of(audit);
        List<FlowAudit> audits = flowAuditService.getExample(example);
        if (!audits.isEmpty()){
            throw new BusinessException(HttpStatus.FORBIDDEN,"重复操作");
        }
        audit.setRemark(params.getRemark());
        flowAuditService.saveOne(audit);
        // 触发流程自检
        context.publishEvent(new FlowCheckEvent(this,flowWork,audit));
        return true;
    }

    /**
     * 验证当前流程节点用户操作权限
     * @param flowCode
     * @param flowNodeId
     */
    public boolean checkActionPerm(String flowCode, String flowNodeId){
        List<FlowTaskAssign> assigns = flowTaskAssignService.getByFlowCodeAndFlowNodeId(flowCode, flowNodeId);
        // 默认为无权限操作
        boolean hasPerm = false;
        // 获取当前用户ID
        String userId = authService.getUid();
        List<String> userIds = taskUserLoader.load(assigns);
        if (authService.isAdmin()){
            hasPerm = true;
        }
        if (userIds.contains(userId)){
            hasPerm = true;
        }
        return hasPerm;
    }

    /**
     * 验证流程操作权限
     * @param flowWork
     */
    public boolean checkActionPerm(FlowWork flowWork){
        // 判断当前用户是否有权操作
        return checkActionPerm(flowWork.getFlowCode(),flowWork.getCurrentNodeId());
    }

    @Override
    public FlowWork modifyFlowStatus(String flowWorkId, FlowWorkStatusEnum statusEnum) {
        FlowWork flowWork = flowWorkService.getOneUnwrap(flowWorkId);
        return modifyFlowStatus(flowWork, statusEnum);
    }

    @Override
    public FlowWork modifyFlowStatus(FlowWork flowWork, FlowWorkStatusEnum statusEnum) {
        flowWork.setFlowWorkStatus(statusEnum.value);
        return flowWorkService.updateOne(flowWork);
    }


    @Override
    public void invokeEvent(FlowWork flowWork, FlowEvent event) {
        // 获取事件信息

    }


}
